
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
#endregion

namespace XNALibrary
{
    /// <summary>
    ///  Base class for all kinds of cameras
    /// </summary>
    public partial class Camera : ICamera
    {
        #region Fields and properties

        protected GraphicsDevice graphics;
        protected InputHandler   input;   // Allow using input to change camera parameters
                                         
        // Projection parameters
        protected Matrix        projection;
        protected bool          projectionChanged = true;   // Have projection parameters changed? 
        protected float         fieldOfView = MathHelper.ToRadians(45.0f);
        protected float         aspectRatio = 1.0f;
        protected float         nearPlane = 1.0f;
        protected float         farPlane = 10000.0f;

        // View and camera parameters
        protected Matrix        view;
        protected bool          viewChanged = true;   // Have view parameters changed?
        // Note: all the following vectors are in the world space
        protected Vector3       position = new Vector3(0.0f, 0.0f, 3.5f);
        protected Vector3       target = new Vector3(0.0f, 0.0f, 2.5f);   
        protected Vector3       aimDirection = new Vector3(0.0f, 0.0f, -1.0f);    
        protected Vector3       upVector = Vector3.UnitY;       // camera local Y axis ==> Yaw
        protected Vector3       rightVector = Vector3.UnitX;    // Camera local X axis ==> Pitch
        protected Vector3       backVector = Vector3.UnitZ;     // camera local Z axis ==> Roll
                                                                // backVector should be negative of aimDirection
        protected Quaternion    rotation;

        public InputHandler Input
        {
            get { return input; }
            set { input = value; }
        }

        public Matrix View
        { 
            get 
            {
                if (viewChanged)
                {
                    Matrix.CreateLookAt(ref position, ref target, ref upVector, out view);
                    viewChanged = false;
                }
                return view; 
            } 
        }

        public Matrix Projection
        {
            get
            {
                if (projectionChanged)
                {
                    Matrix.CreatePerspectiveFieldOfView(fieldOfView, aspectRatio,
                        nearPlane, farPlane, out projection);   
                    projectionChanged = false;
                }
                return projection;
            } 
        }

        public float AspectRatio
        {
            get { return aspectRatio; }
            set { aspectRatio = value; projectionChanged = true; }
        }

        public Vector3 Position
        {
            get { return position; }
            set { SetPosition(value); }
        }

        public Vector3 GetPosition()
        {
            return Position;
        }

        public Vector3 AimDirection
        {
            get { return (aimDirection); }
            set { SetAimDirection(value); }
        }

        public Vector3 Target
        {
            get { return (target); }
            set { SetTarget(value); }
        }

        public Vector3 UpVector
        {
            get { return (upVector); }
            set { upVector = value; viewChanged = true; }
        }

        public Vector3 RightVector
        {
            get { return (rightVector); }
            set { rightVector = value; viewChanged = true; }
        }

        public Vector3 BackVector
        {
            get { return (backVector); }
            set { backVector = value; viewChanged = true; }
        }

        public Quaternion Rotation
        {
            get { return rotation; }
        }

        #endregion Fields and properties
      
        public Camera(GraphicsDevice graphicsDevice)
        {
            graphics  = graphicsDevice;
            input     = null;

            makeCameraViewProj();
        }
  
        public Camera(GraphicsDevice graphicsDevice, InputHandler input)
        {
            graphics = graphicsDevice;
            this.input = input;

            makeCameraViewProj();
        }

        // This Update method should be called from Game.Update
        public virtual void Update(GameTime gameTime)
        {
            if (input != null)
            {
                KeyboardHandler key = input.KeyboardHandler;           
                // Zoom out if in Front or Top view
               if (key.IsKeyDown(Keys.Z))
               {
                    Vector3 lookVector = target - position;
                    SetPosition(position - .5f * lookVector);
                }
                // Zoom in if in front or top view
                if (key.IsKeyDown(Keys.X))
                {
                    Vector3 lookVector = target - position;
                    SetPosition(position + .5f * lookVector);
                }

                // Yaw in front view
                if (key.IsKeyDown(Keys.Y)) // turn frontCamera to left
                {
                    Yaw(1.0f);
                }
                if (key.IsKeyDown(Keys.U)) // turn frontCamera to right
                {
                    Yaw(-1.0f);
                }
            }
        }

        private void makeCameraViewProj()
        {
            // Make projection matrix
            aspectRatio = (float)graphics.Viewport.Width / (float)graphics.Viewport.Height;
            Matrix.CreatePerspectiveFieldOfView(fieldOfView, aspectRatio,
                nearPlane, farPlane, out projection);
            projectionChanged = false;

            // Make view matrix
            Matrix.CreateLookAt(ref position, ref target, ref upVector, out view);
            viewChanged = false;
        }

        public void SetPosition(Vector3 pos) // assuming target and upVector are the same
        {
            position = pos;

            aimDirection = target - pos;
            aimDirection.Normalize();
            backVector = -aimDirection;
            Vector3.Cross(ref upVector, ref backVector, out rightVector);
            rightVector.Normalize();
            upVector.Normalize();
            
            viewChanged = true;
        }

        public void SetTarget(Vector3 _target) // assuming positon and upVector are the same
        {
            target = _target;

            aimDirection = target - position;
            aimDirection.Normalize();
            backVector = -aimDirection;
            Vector3.Cross(ref upVector, ref backVector, out rightVector);
            rightVector.Normalize();

            viewChanged = true;
        }

        public void Yaw(float angle)  // angle in degree
        {
            Matrix m = Matrix.CreateFromAxisAngle(upVector, MathHelper.ToRadians(angle));

            backVector = Vector3.Transform(backVector, m);
            rightVector = Vector3.Transform(rightVector, m);

            updateRotation();
        }

        public void Pitch(float angle)  // angle in degree
        {
            Matrix m = Matrix.CreateFromAxisAngle(rightVector, MathHelper.ToRadians(angle));

            backVector = Vector3.Transform(backVector, m);
            upVector = Vector3.Transform(upVector, m);

            updateRotation();
        }

        public void Roll(float angle)  // angle in degree
        {
            Matrix m = Matrix.CreateFromAxisAngle(aimDirection, MathHelper.ToRadians(angle));

            rightVector = Vector3.Transform(rightVector, m);
            upVector = Vector3.Transform(upVector, m);

            updateRotation();
        }

        private void updateRotation()
        {
            aimDirection = -backVector;
            target = position + aimDirection;

            rotation = Quaternion.CreateFromRotationMatrix
                        (MatrixHelper.CreateMatrixRow(rightVector, upVector, backVector));

            viewChanged = true;
        }

        public void SetAimDirection(Vector3 _aimDirection) // assuming upVector is the same
        {
            aimDirection = _aimDirection;
            aimDirection.Normalize();
            target = position + aimDirection;

            viewChanged = true;
        }

        public void SetViewTarget (Vector3 pos, Vector3 _target, Vector3 up)
        {
            position = pos;
            target   = _target;
            upVector = up;

            aimDirection = target - pos;
            aimDirection.Normalize();
            target = position + aimDirection;

            viewChanged = true;
        }

        public void SetViewAim (Vector3 pos, Vector3 _aimDirection, Vector3 up)
        {
            position = pos;
            upVector = up;
            Vector3.Normalize(ref _aimDirection, out aimDirection);
            target = position + aimDirection;

            viewChanged = true;

        }

        public void SetProjection(float near, float far, float fov)
        {
            nearPlane   = near;
            farPlane    = far;
            fieldOfView = fov;

            projectionChanged = true;
        }


        #region ICamera Members
        

        public string GetGuide()
        {
            return "";
        }

        public string GetViewName()
        {
            return "Static veiw from " + position + " looking towards " + (target - position);
        }
        #endregion

    }
}